/* 
M 	x,y
m 	x,y
 
L 	x,y
l 	x,y
 
H 	x
h 	x
 
V 	y
v 	y
 
C 	x1,y1 x2,y2 x,y
c 	x1,y1 x2,y2 x,y
 
S 	x2,y2 x,y
s 	x2,y2 x,y
 
Q 	x1,y1 x,y
q 	x1,y1 x,y
 
T 	x,y
t 	x,y
 
A 	rx,ry x-axis-rotation large-arch-flag, sweepflag x,y 
a 	rx,ry x-axis-rotation large-arch-flag, sweepflag x,y
 
Z 	  	closepath
z 	  	closepath
*/
package org.anddev.andengine.extension.svg;
/**
 * @author Eric Depta
 * @since 01:14:00 - 28.02.2011
 */
import static org.anddev.andengine.extension.physics.box2d.util.constants.PhysicsConstants.PIXEL_TO_METER_RATIO_DEFAULT;

import java.util.ArrayList;
import java.util.List;

import org.anddev.andengine.entity.primitive.Rectangle;
import org.anddev.andengine.entity.shape.Shape;
import org.anddev.andengine.extension.physics.box2d.util.triangulation.EarClippingTriangulator;
import org.anddev.andengine.extension.svg.util.math.CubicBezier;
import org.anddev.andengine.extension.svg.util.math.Ellipse;
import org.anddev.andengine.extension.svg.util.math.QuadraticBezier;
import org.anddev.andengine.extension.svg.util.math.SVGMathUtils;
import org.anddev.andengine.extension.svg.util.transform.TransformDoc;
import org.anddev.andengine.util.MathUtils;

import android.util.Log;

import com.badlogic.gdx.math.Vector2;

public class SVGPath {
	private Vector2 tmpVec = new Vector2();
	
	private Vector2 mPen = new Vector2(0,0);
	
	private char mLastOperation;
	private String mPath = "";
	private int q = 0;
	private List<Vector2> mVertices = new ArrayList<Vector2>();
	
	private Vector2 lastCubicBezierEndControl;
	private Vector2 lastQuadraticBezierEndControl;
	
	private boolean isClosed;
	private Shape AABB;
	
	// maybe try to make smallestLine for that
	private int segments; //- for curve details 
	
	public SVGPath(final String pPath, final int pSegments){
		this.mPath = pPath;
		this.q = 0;
		this.segments = pSegments;
		this.nextOparation();
	}
	
	public SVGPath(final List<Vector2> pVertices, final boolean pIsClosed){
		this.mVertices = pVertices;
		this.isClosed = pIsClosed;
		this.computeAABB();
		this.localizeVerticesToAABB();
	}
	
	
	private void nextOparation(){
		char currChar;
		while(this.q < this.mPath.length()){
			currChar = this.mPath.charAt(this.q);
			if(currChar!=' '){
				this.doOperation(currChar);
				break;
			}else{
				this.q ++;
			}
		}
	}
	
	private void doOperation(final char pChar){
		boolean doLast = false;
		switch(pChar){
			case 'M':
				this.q++;
				this.moveTo_abs();
				this.mLastOperation = 'L'; //-- if move to next default opp is line to
				break;
			case 'm':
				this.q++;
				this.moveTo_rel();
				this.mLastOperation = 'l'; //-- if move to next default opp is line to
				break;
				
			case 'L':
				this.q++;
				this.lineTo_abs();
				this.mLastOperation = pChar;
				break; 
			case 'l':
				this.q++;
				this.lineTo_rel();
				this.mLastOperation = pChar;
				break;
			 
			case 'H':
				this.q++;
				this.hLineTo_abs();
				this.mLastOperation = pChar;
				break;
			case 'h':
				this.q++;	
				this.hLineTo_rel();
				this.mLastOperation = pChar;
				break;
			 
			case 'V':
				this.q++;
				this.vLineTo_abs();
				this.mLastOperation = pChar;
				break;
			case 'v':
				this.q++;
				this.vLineTo_rel();
				this.mLastOperation = pChar;
				break;
			 
			case 'C':
				this.q++;
				this.cubicBezier_abs();
				this.mLastOperation = pChar;
				break;
			case 'c':
				this.q++;
				this.cubicBezier_rel();
				this.mLastOperation = pChar;
				break;
			 
			case 'S':
				this.q++;
				this.sCubicBezier_abs();
				this.mLastOperation = pChar;
				break;
			case 's':
				this.q++;
				this.sCubicBezier_rel();
				this.mLastOperation = pChar;
				break;
			 
			case 'Q':
				this.q++;	
				this.quadraticBezier_abs();
				this.mLastOperation = pChar;
				break;
			case 'q':
				this.q++;
				this.quadraticBezier_rel();
				this.mLastOperation = pChar;
				break;
			 
			case 'T':
				this.q++;				
				this.sQuadraticBezier_abs();
				this.mLastOperation = pChar;
				break;
			case 't':
				this.q++;				
				this.sQuadraticBezier_rel();
				this.mLastOperation = pChar;
				break;
			 
			case 'A':
				this.q++;				
				this.arc_abs();
				this.mLastOperation = pChar;
				break;
			case 'a':
				this.q++;				
				this.arc_rel();
				this.mLastOperation = pChar;
				break;
			 
			case 'Z':
				this.q++;				
				this.close();
				this.mLastOperation = 'L'; //- not needed 
				break;
			case 'z':
				this.q++;				
				this.close();
				this.mLastOperation = 'l'; //- not needed
				break;
				
			default:
				//-- Last Operation 
				if(this.q!=this.mPath.length()-1 && this.mLastOperation!='Z' || this.mLastOperation!='z'){
					this.q--;
					doLast = true;
				}
				break;
		}
		if(doLast){
			this.doOperation(this.mLastOperation);
		}else if(this.q == this.mPath.length()){
			// last step
			this.computeAABB();
			this.localizeVerticesToAABB();
		}else{
			this.nextOparation();
		}
		
	}
	
	//-- Move to
	private void moveTo_rel(){
		this.mPen.add(this.getPoint());
		this.mVertices.add(this.mPen.cpy());
	}
	private void moveTo_abs(){
		this.mPen.set(this.getPoint());
		this.mVertices.add(this.mPen.cpy());
	}
	
	//-- Line to
	private void lineTo_rel(){
		this.moveTo_rel();
	}
	private void lineTo_abs(){
		this.moveTo_abs();
	}
	
	//-- Horizontal Line to
	private void hLineTo_rel(){
		this.mPen.set(this.mPen.x + this.getValue(),this.mPen.y);
		this.mVertices.add(this.mPen.cpy());
	}
	private void hLineTo_abs(){
		this.mPen.set(this.getValue(),this.mPen.y);
		this.mVertices.add(this.mPen.cpy());
	}
	
	//-- Vertical Line to
	private void vLineTo_rel(){
		this.mPen.set(this.mPen.x, this.mPen.y + this.getValue());
		this.mVertices.add(this.mPen.cpy());
	}
	private void vLineTo_abs(){
		this.mPen.set(this.mPen.x, this.getValue());
		this.mVertices.add(this.mPen.cpy());
	}
	
	//-- Cubic Bezier 
	private void cubicBezier_rel(){
		final Vector2[] points = this.getPoints(3);
		this.cubicBezier(this.mPen, points[0].add(this.mPen), points[1].add(this.mPen), points[2].add(this.mPen));
	}
	private void cubicBezier_abs(){
		final Vector2[] points = this.getPoints(3);
		this.cubicBezier(this.mPen, points[0], points[1], points[2]);
	}
	private void cubicBezier(final Vector2 p0, final Vector2 p1, final Vector2 p2, final Vector2 p3){
		CubicBezier.cubicBezier(this.mVertices, this.segments, p0, p1, p2, p3);
		this.mPen.set(p3);
		this.lastCubicBezierEndControl = p2.cpy();
	}
	
	//-- Smooth Cubic bezier
	private void sCubicBezier_rel(){
		if(this.mLastOperation=='c' || this.mLastOperation=='C' || this.mLastOperation=='s' || this.mLastOperation=='S'){
			//-- start controll is reflection from last end controll on pen
			this.lastCubicBezierEndControl.sub(this.mPen);
			this.lastCubicBezierEndControl.mul(-1);
		}else{
			//-- start controll pen
			this.lastCubicBezierEndControl.set(this.mPen);
		}
		final Vector2[] points = this.getPoints(2);
		this.cubicBezier(this.mPen, this.lastCubicBezierEndControl, points[0].add(this.mPen), points[1].add(this.mPen));
	}
	private void sCubicBezier_abs(){
		if(this.mLastOperation=='c' || this.mLastOperation=='C' || this.mLastOperation=='s' || this.mLastOperation=='S'){
			//-- start controll is reflection from last end controll on pen
			this.lastCubicBezierEndControl.sub(this.mPen);
			this.lastCubicBezierEndControl.mul(-1);
		}else{
			//-- start controll pen
			this.lastCubicBezierEndControl.set(this.mPen);
		}
		final Vector2[] points = this.getPoints(2);
		this.cubicBezier(this.mPen, this.lastCubicBezierEndControl, points[0], points[1]);
	}
	
	
	//-- Quadratic Bezier
	private void quadraticBezier_rel(){
		final Vector2[] points = this.getPoints(2);
		this.quadraticBezier(this.mPen, points[0].add(this.mPen), points[1].add(this.mPen));
	}
	private void quadraticBezier_abs(){
		final Vector2[] points = this.getPoints(2);
		this.quadraticBezier(this.mPen, points[0], points[1]);
	}
	private void quadraticBezier(final Vector2 p0, final Vector2 p1, final Vector2 p2){
		QuadraticBezier.quadraticBezier(this.mVertices, this.segments, p0, p1, p2);
		this.mPen.set(p2);
		this.lastQuadraticBezierEndControl = p1.cpy();
	}
	
	//-- Smooth Quadratic Bezier
	private void sQuadraticBezier_rel(){
		if(this.mLastOperation=='q' || this.mLastOperation=='Q' || this.mLastOperation=='t' || this.mLastOperation=='T'){
			//-- start controll is reflection from last end controll on pen
			this.lastQuadraticBezierEndControl.sub(this.mPen);
			this.lastQuadraticBezierEndControl.mul(-1);
		}else{
			//-- start controll pen
			this.lastQuadraticBezierEndControl.set(this.mPen);
		}
		final Vector2 point = this.getPoint();
		this.quadraticBezier(this.mPen, this.lastCubicBezierEndControl, point.add(this.mPen));
	}
	private void sQuadraticBezier_abs(){
		if(this.mLastOperation=='q' || this.mLastOperation=='Q' || this.mLastOperation=='t' || this.mLastOperation=='T'){
			//-- start controll is reflection from last end controll on pen
			this.lastQuadraticBezierEndControl.sub(this.mPen);
			this.lastQuadraticBezierEndControl.mul(-1);
		}else{
			//-- start controll pen
			this.lastQuadraticBezierEndControl.set(this.mPen);
		}
		final Vector2 point = this.getPoint();
		this.quadraticBezier(this.mPen, this.lastCubicBezierEndControl, point);
	}
	
	//-- Arch
	private void arc_rel(){
		this.arc(
			this.mPen, 
			this.getValue(), 
			this.getValue(), 
			this.getValue(), 
			this.getValueBool(), 
			this.getValueBool(), 
			this.getPoint().add(this.mPen)
		);
	}
	private void arc_abs(){
		this.arc(
				this.mPen, 
				this.getValue(), 
				this.getValue(), 
				this.getValue(), 
				this.getValueBool(), 
				this.getValueBool(), 
				this.getPoint()
			);
	}
	
	private void arc(Vector2 p0, float rx, float ry, float angle, boolean largeArcFlag, boolean sweepFlag, Vector2 p1){
		//
		// extract from baltik: ExtendedGeneralPath.java
		// see also: http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
        // Elliptical arc implementation based on the SVG specification notes
        //

        //- Compute the half distance between the current and the final point
        float dx2 = (p0.x - p1.x) / 2f;
        float dy2 = (p0.y - p1.y) / 2f;
        //- Convert angle from degrees to radians
        angle = MathUtils.degToRad(angle % 360f);
        float cosAngle = (float) Math.cos(angle);
        float sinAngle = (float) Math.sin(angle);

        //- Step 1 : Compute (x1, y1)
        float x1 = (cosAngle * dx2 + sinAngle * dy2);
        float y1 = (-sinAngle * dx2 + cosAngle * dy2);
        //- Ensure radii are large enough
        rx = Math.abs(rx);
        ry = Math.abs(ry);
        float Prx = rx * rx;
        float Pry = ry * ry;
        float Px1 = x1 * x1;
        float Py1 = y1 * y1;
        //- check that radii are large enough
        float radiiCheck = Px1/Prx + Py1/Pry;
        if (radiiCheck > 1) {
            rx = (float) (Math.sqrt(radiiCheck) * rx);
            ry = (float) (Math.sqrt(radiiCheck) * ry);
            Prx = rx * rx;
            Pry = ry * ry;
        }

        //- Step 2 : Compute (cx1, cy1)
        float sign = (largeArcFlag == sweepFlag) ? -1 : 1;
        float sq = ((Prx*Pry)-(Prx*Py1)-(Pry*Px1)) / ((Prx*Py1)+(Pry*Px1));
        sq = (sq < 0) ? 0 : sq;
        float coef = (float) (sign * Math.sqrt(sq));
        float cx1 = coef * ((rx * y1) / ry);
        float cy1 = coef * -((ry * x1) / rx);

        //- Step 3 : Compute (cx, cy) from (cx1, cy1)
        final Vector2 center = new Vector2(
        	((p0.x + p1.x) / 2f) + (cosAngle * cx1 - sinAngle * cy1),
        	((p0.y + p1.y) / 2f) + (sinAngle * cx1 + cosAngle * cy1)
        );

        //- Step 4 : Compute the angleStart (angle1) and the angleExtent (dangle)
        float ux = (x1 - cx1) / rx;
        float uy = (y1 - cy1) / ry;
        float vx = (-x1 - cx1) / rx;
        float vy = (-y1 - cy1) / ry;
        float p, n;
        // Compute the angle start
        n = (float) Math.sqrt((ux * ux) + (uy * uy));
        p = ux; // (1 * ux) + (0 * uy)
        sign = (uy < 0) ? -1f : 1f;
        float angleStart = MathUtils.radToDeg(sign * (float)Math.acos(p / n));

        //- Compute the angle extent
        n = (float) Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy));
        p = ux * vx + uy * vy;
        sign = (ux * vy - uy * vx < 0) ? -1f : 1f;
        float angleExtent = MathUtils.radToDeg(sign * (float)Math.acos(p / n));
        if(!sweepFlag && angleExtent > 0) {
            angleExtent -= 360f;
        } else if (sweepFlag && angleExtent < 0) {
            angleExtent += 360f;
        }
        angleExtent %= 360f;
        angleStart %= 360f;
        
        //-- add
        Ellipse.ellipse(this.mVertices, this.segments, center, rx, ry, angle, angleStart, angleExtent);
        this.mPen.set(Ellipse.ellipsePoint(center, rx, ry, angle, angleStart+angleExtent));
	}
	
	//-- Close
	private void close(){
		//-- clear Last Vertice cause bezier curves close it in params
		if(this.mLastOperation == 'C' || this.mLastOperation == 'c' ||
		this.mLastOperation == 'S' || this.mLastOperation == 's' ||
		this.mLastOperation == 'Q' || this.mLastOperation == 'q' || 
		this.mLastOperation == 'T' || this.mLastOperation == 't'){
			this.mVertices.remove(this.mVertices.size()-1);
		}
		this.isClosed = true;
		//- No islands
		this.q = this.mPath.length();
	}
	
	//-- util
	private Vector2[] getPoints(final int cnt){
		Vector2[] returnVecs = new Vector2[cnt];
		for(int i=0;i<cnt;i++){
			returnVecs[i] = this.getPoint().cpy();
		}
		return returnVecs;
	}
	
	private Vector2 getPoint(){
		int valCnt = 0;
		String val = "";
		
		while(this.q < this.mPath.length()){
			char currChar = this.mPath.charAt(this.q);
			if(currChar == ',' || (currChar == ' ' && val!="" && valCnt == 0)){
				this.tmpVec.x = Float.parseFloat(val);
				val = "";
				valCnt = 1;
			}else if(valCnt == 1 && currChar == ' '){
				this.tmpVec.y = Float.parseFloat(val);
				this.q++;
				break;
			}else{
				val += currChar;
				if(valCnt == 1 && this.q == this.mPath.length()-1){
					this.tmpVec.y = Float.parseFloat(val);
					this.q++;
					break;
				}
			}
			
			this.q++;
		}
		return this.tmpVec;
	}
	
	private float getValue(){
		String strval = "";
		float val = 0;
		while(this.q < this.mPath.length()){
			char currChar = this.mPath.charAt(this.q);
			if((currChar == ',' || currChar == ' ') && strval!=""){
				val = Float.parseFloat(strval);
				break;
			}else if(currChar != ','){
				strval += currChar;
			}
			this.q++;
		}
		return val;
	}
	
	private boolean getValueBool(){
		if(this.getValue()==1){
			return true;
		}else{
			return false;
		}
	}
	
	public Vector2 getPen(){
		return this.mPen;
	}
	
	private void computeAABB(){
		if(this.isValid()){
			float minX = Math.min(this.mVertices.get(0).x, this.mVertices.get(1).x);
			float maxX = Math.max(this.mVertices.get(0).x, this.mVertices.get(1).x);
			float minY = Math.min(this.mVertices.get(0).y, this.mVertices.get(1).y);
			float maxY = Math.max(this.mVertices.get(0).y, this.mVertices.get(1).y);
			
			for(int i=2;i<this.mVertices.size();i++){
				final Vector2 vec = this.mVertices.get(i);
				minX = Math.min(minX, vec.x);
				maxX = Math.max(maxX, vec.x);
				minY = Math.min(minY, vec.y);
				maxY = Math.max(maxY, vec.y);
			}
			
			this.AABB = new Rectangle(minX, minY, maxX - minX, maxY - minY);
		}
	}
	
	
	private void localizeVerticesToAABB(){
		if(this.isValid()){
			for(int i=0;i<this.mVertices.size();i++){
				this.mVertices.get(i).x -= this.AABB.getX();
				this.mVertices.get(i).y -= this.AABB.getY();
			}
		}else{
			Log.e("not valid","no");
		}
	}
	
	
	public Shape getAABB(){
		return this.AABB;
	}
	
	public Vector2[] getVertices(){
		return (Vector2[]) this.mVertices.toArray(new Vector2[this.mVertices.size()]);
	}
	
	public List<Vector2> getVerticesList(){
		return this.mVertices;
	}
	
	public Vector2[] getPhysicVertices(final TransformDoc pTransform){
		return (Vector2[]) this.getPhysicVerticesList(pTransform).toArray(new Vector2[this.mVertices.size()]);
	}

	public List<Vector2> getPhysicVerticesList(final TransformDoc pTransform){
		List<Vector2> physVecs = new ArrayList<Vector2>();
		float[] center = new float[2];
		// maybe rotation center
		center[0] = this.AABB.getWidth() * 0.5f;
		center[1] = this.AABB.getHeight() * 0.5f;
		center = SVGMathUtils.PointToObjectWorld(pTransform, center);
		center[0] /= PIXEL_TO_METER_RATIO_DEFAULT;
		center[1] /= PIXEL_TO_METER_RATIO_DEFAULT;
		
		for(int i=0;i<this.mVertices.size();i++){
			this.tmpVec.set(this.mVertices.get(i));
			SVGMathUtils.VectorToObjectWorld(pTransform, this.tmpVec);
			this.tmpVec.x = this.tmpVec.x / PIXEL_TO_METER_RATIO_DEFAULT - center[0];
			this.tmpVec.y = this.tmpVec.y / PIXEL_TO_METER_RATIO_DEFAULT - center[1];
			
			//rotation zero cause need set by physic see PathNode
			float[] point = SVGMathUtils.vector2ToFloat(this.tmpVec);
			point = MathUtils.rotateAroundCenter(point, -pTransform.getRotation(), 0, 0);
			SVGMathUtils.floatIntoVector2(point,this.tmpVec);
			
			physVecs.add(this.tmpVec.cpy());
		}
		
		return physVecs;
	}
	
	public List<Vector2> getPhysicVerticesTriangleList(final TransformDoc pTransform){
		final EarClippingTriangulator triangulator = new EarClippingTriangulator();
		return triangulator.computeTriangles(this.getPhysicVerticesList(pTransform));
	}
	
	//--
	
	public List<Vector2> getVerticesTriangleList(){
		final EarClippingTriangulator triangulator = new EarClippingTriangulator();
		return triangulator.computeTriangles(this.getVerticesList());
	}
	
	public int VericesLength(){
		return this.mVertices.size();
	}
	
	public boolean isValid(){
		return (this.mVertices.size() >= 2);
	}
	
	public boolean isClosed(){
		return this.isClosed;
	}
	
	public void LogVertices(){
		for(int i=0;i<this.mVertices.size();i++){
			Log.i("Vert "+i,"x="+this.mVertices.get(i).x+" y="+this.mVertices.get(i).y);
		}
	}
	
}

